package org.example.service.impl;

import cn.hutool.core.date.DateUtil;
import org.example.cache.RedisUtil;
import org.example.dto.RankingDto;
import org.example.service.RankingService;
import org.example.vo.RankingVo;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.*;

/**
 * @FileName RankingServiceImpl
 * @Description 排行榜服务实现类
 * @Author coderfun
 * @selfDesc coding in fun
 * @date 2024-11-04
 **/
@Service
public class RankingServiceImpl implements RankingService {
    @Resource
    private RedisUtil redisUtil;
    private DefaultRedisScript<String> redisScript;
    /*排行榜业务名称*/
    private static final String BUSI = "run_hero";
    /*排行榜默认查询前多少行*/
    private static final Integer ZSET_DEFAULT_MAX_SIZE = 500;
    //分段大小默认为10
    private static final Integer SEGMENT_DEFAULT_SIZE = 10;
    //默认时间日期
    private static final Date DATE_2050 = DateUtil.parse("2050-01-01");

    @Override
    public boolean add(RankingDto rankingDto) {
        try {
            // 获取当前时间
            Date now = DateUtil.date();
            // 计算两个日期之间的差值，单位为毫秒
            long diffInMillis = DateUtil.betweenMs(now, DATE_2050);
            double score = rankingDto.getScore() + diffInMillis;
            //判断zset长度，如果超过500，就添加Zset和Hash，如果不超过500就直接往Hash中添加
            Long zsetLength = redisUtil.zSize(BUSI);
            String userId = String.valueOf(rankingDto.getUserId());
            Boolean b = redisUtil.zAdd(BUSI, userId, score);
            // 根据score来设置Hash段，求出排名第501的score和最小的score来进行分段
            if (zsetLength > 500) {
                Set<ZSetOperations.TypedTuple<String>> outFirstZsetItem = redisUtil.zReverseRangeWithScores(BUSI, ZSET_DEFAULT_MAX_SIZE, ZSET_DEFAULT_MAX_SIZE);
                // 获取 outFirstZsetItem 的唯一元素及其分值,因为只有一个元素， zset之外最小的积分肯定是0，所以不用get
                ZSetOperations.TypedTuple<String> outFirstTuple = outFirstZsetItem.iterator().next();
                double outFirstScore = outFirstTuple.getScore();
                //如果Hash不存在那就构建
                if (!redisUtil.hasKey(BUSI + "_hash")) {
                    List<String> req = divideRangeIntoSegments(0, (int) outFirstScore);
                    //构建hash结构
                    req.forEach(s -> {
                        redisUtil.hPut(BUSI + "_hash", s, String.valueOf(0));
                    });
                } else {
                    //如果存在，那就记录范围内的数量
                    Map<Object, Object> objectObjectMap = redisUtil.hGetKeyAndValue(BUSI + "_hash");
                    for (Map.Entry<Object, Object> entry : objectObjectMap.entrySet()) {
                        String key = (String) entry.getKey();
                        String[] split = key.split("-");
                        int start = Integer.parseInt(split[0]);
                        int end = Integer.parseInt(split[1]);
                        if (rankingDto.getScore() >= start && rankingDto.getScore() <= end) {
                            //如果落在这个分段，就加一
                            redisUtil.hIncrBy(BUSI + "_hash", key, 1);
                        }
                    }
                }
            }
            return b;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    //TODO 值可做修改，比如可以展示前100个分页，每页50个排行榜
    @Override
    public List<RankingVo> select() {
        ArrayList<RankingVo> res = new ArrayList<>();
        Set<ZSetOperations.TypedTuple<String>> typedTuples = redisUtil.zReverseRangeWithScores(BUSI, 0, ZSET_DEFAULT_MAX_SIZE);
        for (ZSetOperations.TypedTuple<String> typedTuple : typedTuples) {
            res.add(new RankingVo(Long.parseLong(typedTuple.getValue()), typedTuple.getScore()));
        }
        return res;
    }

    @Override
    public Integer selectMyRanking(RankingDto rankingDto) {
        // 默认排名第一名
        int rank = 1;
        Set<ZSetOperations.TypedTuple<String>> typedTuples = redisUtil.zReverseRangeWithScores(BUSI, 0, ZSET_DEFAULT_MAX_SIZE);
        for (ZSetOperations.TypedTuple<String> typedTuple : typedTuples) {
            if (rankingDto.getUserId() == Long.parseLong(typedTuple.getValue())) {
                //如果前500名中找到了直接返回
                return rank;
            } else {
                // 没找到就加一
                rank++;
            }
        }
        //TODO 如果不是前500名，就根据Hash结构粗估排名
        return null;
    }

    /**
     * 将指定的数字范围分割成最多10个等长的段
     * 如果起始数字大于结束数字，会自动交换两个数字
     * 此方法解释了如何将一个大的数字范围平均分割成小段，便于处理或显示
     *
     * @param start 范围的起始数字
     * @param end   范围的结束数字
     * @return 包含范围段的列表，每个段以字符串形式表示
     */
    public List<String> divideRangeIntoSegments(int start, int end) {
        if (start > end) {
            int temp = start;
            start = end;
            end = temp;
        }

        int segmentLength = (end - start + 1) / SEGMENT_DEFAULT_SIZE;
        int remainder = (end - start + 1) % SEGMENT_DEFAULT_SIZE;

        List<String> segments = new ArrayList<>();
        int currentStart = start;

        for (int i = 0; i < SEGMENT_DEFAULT_SIZE; i++) {
            int currentEnd = currentStart + segmentLength - 1;
            if (i < remainder) {
                currentEnd++;
            }

            segments.add(currentStart + "-" + currentEnd);
            currentStart = currentEnd + 1;
        }

        return segments;
    }
}
